#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include "lcd_gui.h"
#include "lcd_2inch4.h"

IMAGE Image;

static uint16_t wire_color(uint16_t color)
{
    return (uint16_t)(((color << 8) & 0xFF00U) | (color >> 8));
}


void Image_Init(uint16_t *img, uint16_t img_w, uint16_t img_h, uint16_t rotate, uint16_t mirror)
{
    Image.img = img;
    Image.w = img_w;
    Image.h = img_h;
    Image.rotate = rotate;
    Image.mirror = mirror;
    if (Image.rotate == ROTATE_90 || Image.rotate == ROTATE_270)
    {
        Image.h = img_w;
        Image.w = img_h;
    }
    printf("img w*h-%d*%d\r\n", Image.w, Image.h);
}

void Gui_set_pix(uint16_t x, uint16_t y, uint16_t color)
{
    uint16_t xx = 0;
    uint16_t yy = 0;

    if (x >= Image.w || y >= Image.h)
        return;

    switch(Image.rotate)
    {
        case ROTATE_0:
            xx = x;
            yy = y;
            break;
        case ROTATE_90:
            xx = (uint16_t)(LCD_W - y - 1U);
            yy = x;
            break;
        case ROTATE_180:
            xx = (uint16_t)(LCD_W - x - 1U);
            yy = (uint16_t)(LCD_H - y - 1U);
            break;
        case ROTATE_270:
            xx = y;
            yy = (uint16_t)(LCD_H - x - 1U);
            break;
        default:
            return;
    }

    if (xx >= LCD_W || yy >= LCD_H)
        return;

    Image.img[xx + yy * LCD_W] = wire_color(color);
}

void Gui_Clear(uint16_t color)
{
    uint32_t i;
    uint16_t data = wire_color(color);
    for (i = 0; i < (uint32_t)LCD_W * LCD_H; i++)
        Image.img[i] = data;
}

void Gui_draw_point(uint16_t x, uint16_t y, uint16_t color, uint16_t width)
{
    uint16_t xd, yd;
    if (x >= Image.w || y >= Image.h)
        return;

    if (width == 0) width = 1;
    for (xd = 0; xd < width; xd++)
    {
        for (yd = 0; yd < width; yd++)
        {
            if ((uint32_t)x + xd >= Image.w || (uint32_t)y + yd >= Image.h)
                continue;
            Gui_set_pix((uint16_t)(x + xd), (uint16_t)(y + yd), color);
        }
    }
}

void Gui_draw_line(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color, uint16_t width, LINE_STYLE type)
{
    int x = x1;
    int y = y1;
    int dx = abs((int)x2 - (int)x1);
    int sx = x1 < x2 ? 1 : -1;
    int dy = -abs((int)y2 - (int)y1);
    int sy = y1 < y2 ? 1 : -1;
    int err = dx + dy;
    int dot_cnt = 0;

    for (;;)
    {
        dot_cnt++;
        if (type == SOLID || (type == DOTTED && (dot_cnt % 6) < 3))
            Gui_draw_point((uint16_t)x, (uint16_t)y, color, width);
        if (x == (int)x2 && y == (int)y2)
            break;
        {
            int e2 = 2 * err;
            if (e2 >= dy) { err += dy; x += sx; }
            if (e2 <= dx) { err += dx; y += sy; }
        }
    }
}

void Gui_draw_rectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color, uint16_t width, FILL fill)
{
    uint16_t i;
    if (x1 >= Image.w || y1 >= Image.h || x2 >= Image.w || y2 >= Image.h)
        return;

    if (fill == FULL)
    {
        for (i = y1; i <= y2; i++)
            Gui_draw_line(x1, i, x2, i, color, 1, SOLID);
    }
    else
    {
        Gui_draw_line(x1, y1, x2, y1, color, width, SOLID);
        Gui_draw_line(x1, y1, x1, y2, color, width, SOLID);
        Gui_draw_line(x1, y2, x2, y2, color, width, SOLID);
        Gui_draw_line(x2, y1, x2, y2, color, width, SOLID);
    }
}

void Gui_draw_circle(uint16_t x, uint16_t y, uint16_t r, uint16_t color, uint16_t width, FILL fill)
{
    int dx = 0;
    int dy = r;
    int d = 1 - (int)r;

    while (dy >= dx)
    {
        if (fill == EMPTY)
        {
            Gui_draw_point((uint16_t)(x + dx), (uint16_t)(y + dy), color, width);
            Gui_draw_point((uint16_t)(x + dy), (uint16_t)(y + dx), color, width);
            Gui_draw_point((uint16_t)(x - dx), (uint16_t)(y + dy), color, width);
            Gui_draw_point((uint16_t)(x - dy), (uint16_t)(y + dx), color, width);
            Gui_draw_point((uint16_t)(x - dx), (uint16_t)(y - dy), color, width);
            Gui_draw_point((uint16_t)(x - dy), (uint16_t)(y - dx), color, width);
            Gui_draw_point((uint16_t)(x + dx), (uint16_t)(y - dy), color, width);
            Gui_draw_point((uint16_t)(x + dy), (uint16_t)(y - dx), color, width);
        }
        else
        {
            Gui_draw_line((uint16_t)(x - dy), (uint16_t)(y + dx), (uint16_t)(x + dy), (uint16_t)(y + dx), color, 1, SOLID);
            Gui_draw_line((uint16_t)(x - dy), (uint16_t)(y - dx), (uint16_t)(x + dy), (uint16_t)(y - dx), color, 1, SOLID);
            Gui_draw_line((uint16_t)(x - dx), (uint16_t)(y + dy), (uint16_t)(x + dx), (uint16_t)(y + dy), color, 1, SOLID);
            Gui_draw_line((uint16_t)(x - dx), (uint16_t)(y - dy), (uint16_t)(x + dx), (uint16_t)(y - dy), color, 1, SOLID);
        }
        if (d < 0)
            d += 2 * dx + 3;
        else
        {
            d += 2 * (dx - dy) + 5;
            dy--;
        }
        dx++;
    }
}

void Gui_draw_char(uint16_t x, uint16_t y, const char str_char, FONT* Font, uint16_t color, uint16_t background)
{
    uint16_t row, col_bit;
    uint16_t char_nbytes;
    uint32_t offset;
    const unsigned char *ptr;

    if (x >= Image.w || y >= Image.h || str_char < ' ')
        return;

    char_nbytes = (uint16_t)(Font->h * (Font->w / 8 + (Font->w % 8 ? 1 : 0)));
    offset = (uint32_t)(str_char - ' ') * char_nbytes;
    ptr = &Font->table[offset];

    for (row = 0; row < Font->h; row++)
    {
        for (col_bit = 0; col_bit < Font->w; col_bit++)
        {
            if (*ptr & (0x80U >> (col_bit % 8)))
                Gui_set_pix((uint16_t)(x + col_bit), (uint16_t)(y + row), color);
            else if (background != FONT_BACKGROUND)
                Gui_set_pix((uint16_t)(x + col_bit), (uint16_t)(y + row), background);

            if (col_bit % 8 == 7)
                ptr++;
        }
        if (Font->w % 8 != 0)
            ptr++;
    }
}

void Gui_draw_str(uint16_t x, uint16_t y, const char *str_char, FONT* Font, uint16_t color, uint16_t background)
{
    uint16_t dx = x, dy = y;
    while (*str_char != '\0')
    {
        if ((uint32_t)dx + Font->w > Image.w)
        {
            dx = x;
            dy = (uint16_t)(dy + Font->h);
        }
        if ((uint32_t)dy + Font->h > Image.h)
            break;
        Gui_draw_char(dx, dy, *str_char++, Font, color, background);
        dx = (uint16_t)(dx + Font->w);
    }
}

void Gui_draw_str_cn(uint16_t x, uint16_t y, const char *str_cn, CN_FONT* Font, uint16_t color, uint16_t background)
{
    uint16_t dx = x, dy = y;
    uint16_t i, row, col_bit;

    while (*str_cn != '\0' && *(str_cn + 1) != '\0')
    {
        for (i = 0; i < Font->size; i++)
        {
            if ((uint8_t)str_cn[0] == (uint8_t)Font->table[i].context[0] &&
                (uint8_t)str_cn[1] == (uint8_t)Font->table[i].context[1])
            {
                const unsigned char *p_str = (const unsigned char *)&Font->table[i].pix[0];
                for (row = 0; row < Font->h; row++)
                {
                    for (col_bit = 0; col_bit < Font->w; col_bit++)
                    {
                        if (*p_str & (0x80U >> (col_bit % 8)))
                            Gui_set_pix((uint16_t)(dx + col_bit), (uint16_t)(dy + row), color);
                        else if (background != FONT_BACKGROUND)
                            Gui_set_pix((uint16_t)(dx + col_bit), (uint16_t)(dy + row), background);
                        if (col_bit % 8 == 7)
                            p_str++;
                    }
                    if (Font->w % 8 != 0)
                        p_str++;
                }
                break;
            }
        }
        str_cn += 2;
        dx = (uint16_t)(dx + Font->w);
        if ((uint32_t)dx + Font->w > Image.w)
        {
            dx = x;
            dy = (uint16_t)(dy + Font->h);
        }
    }
}

int Gui_show_bmp(const char *path)
{
    FILE *bmp_fd;
    BMP_HEADER bmp_header;
    BMP_INFO bmp_info;
    uint8_t *row_buf = NULL;
    uint32_t row_stride;
    uint32_t src_w;
    uint32_t src_h;
    uint32_t bytes_per_pixel;
    uint32_t y;
    int top_down;
    int x_offset;
    int y_offset;

    Gui_Clear(BLACK);
    bmp_fd = fopen(path, "rb");
    if (bmp_fd == NULL)
    {
        printf("open bmp file failed: %s\r\n", path);
        return -1;
    }

    if (fread(&bmp_header, sizeof(bmp_header), 1, bmp_fd) != 1 ||
        fread(&bmp_info, sizeof(bmp_info), 1, bmp_fd) != 1)
    {
        fclose(bmp_fd);
        return -1;
    }

    if (bmp_header.type != 0x4D42U || bmp_info.planes != 1U ||
        bmp_info.compression != 0U ||
        (bmp_info.bit_count != 24U && bmp_info.bit_count != 32U) ||
        bmp_info.width <= 0 || bmp_info.height == 0)
    {
        printf("unsupported BMP: %s\r\n", path);
        fclose(bmp_fd);
        return -1;
    }

    src_w = (uint32_t)bmp_info.width;
    top_down = bmp_info.height < 0;
    src_h = (uint32_t)(top_down ? -bmp_info.height : bmp_info.height);
    bytes_per_pixel = bmp_info.bit_count / 8U;
    row_stride = (src_w * bytes_per_pixel + 3U) & ~3U;

    if (src_w > Image.w || src_h > Image.h)
    {
        printf("bmp file larger than screen: %s\r\n", path);
        fclose(bmp_fd);
        return -1;
    }

    row_buf = (uint8_t *)malloc(row_stride);
    if (row_buf == NULL)
    {
        fclose(bmp_fd);
        return -1;
    }

    x_offset = ((int)Image.w - (int)src_w) / 2;
    y_offset = ((int)Image.h - (int)src_h) / 2;

    for (y = 0; y < src_h; y++)
    {
        uint32_t file_row = top_down ? y : (src_h - 1U - y);
        uint32_t x;
        long offset = (long)bmp_header.offbits + (long)(file_row * row_stride);
        if (fseek(bmp_fd, offset, SEEK_SET) != 0 ||
            fread(row_buf, row_stride, 1, bmp_fd) != 1)
        {
            free(row_buf);
            fclose(bmp_fd);
            return -1;
        }

        for (x = 0; x < src_w; x++)
        {
            const uint8_t *pixel = &row_buf[x * bytes_per_pixel];
            uint16_t color = BGR888_TO_RGB565(
                pixel[0], pixel[1], pixel[2]);
            Gui_set_pix((uint16_t)(x_offset + (int)x),
                        (uint16_t)(y_offset + (int)y), color);
        }
    }

    free(row_buf);
    fclose(bmp_fd);
    printf("open file %s success\r\n", path);
    return 0;
}
